`include "common_define.vh"
module epu_top(
    input wire epu_clk,
    input wire rst_n,
    //auto-forwarder
    input wire rxfifo_epu_half_full,
    input wire rxfifo_epu_empty,
    input wire [11:0] rxfifo_epu_din,
    output wire epu_rxfifo_re,
    //tx
    output wire epu_tx_clk,
    output wire [7:0] epu_tx_dout,
    //PDI
    input wire pdi_epu_we,
    input wire pdi_epu_re,
    input wire [15:0] pdi_epu_addr,
    input wire [7:0] pdi_epu_din,
    output wire [7:0] epu_pdi_dout,
    output wire epu_pdi_dout_valid
);
    wire ecat_epu_we;
    wire ecat_epu_re;
    wire [15:0] ecat_epu_addr;
    wire [7:0] ecat_epu_din;
    wire [7:0] epu_ecat_dout;
    wire epu_ecat_dout_valid;

    wire crc_check_success;

    wire [3:0] state;
    wire rxfifo_ready;
        
    reg [2:0] cdc_counter;
    //cross clock domain counter
    always@(posedge epu_clk or negedge rst_n)begin
        if (!rst_n) begin
            cdc_counter <= 3'b0;
        end 
        else if(rxfifo_ready) begin
            cdc_counter <= cdc_counter + 1'b1;
        end
        else if(rxfifo_epu_empty && cdc_counter==7) begin
            cdc_counter <= 3'b0;
        end
    end
    /*********************************************************************************
     * read data from rxfifo
     * 1) rxfifo_epu_half_full: indicates that the rxfifo is half full
     * 2) rxfifo_epu_empty: indicates that the rxfifo is empty
     * 3) cdc_counter: when cdc is 0, check epu_half_full and send epu_rxfifo_re
    *********************************************************************************/
    epu_read_rxfifo epu_read_rxfifo_inst (
        .epu_clk(epu_clk),
        .rst_n(rst_n),
        .rxfifo_epu_half_full(rxfifo_epu_half_full),
        .rxfifo_epu_empty(rxfifo_epu_empty),
        .rxfifo_epu_din(rxfifo_epu_din),
        .cdc_counter(cdc_counter),
        .rxfifo_ready(rxfifo_ready),
        .epu_rxfifo_re(epu_rxfifo_re)
    );
    
    //shift register for receiving data
    reg [23:0] shift_reg;

    always@(posedge epu_clk or negedge rst_n)begin
        if(!rst_n)begin
            shift_reg <= 24'b0;
        end
        else if(rxfifo_ready && cdc_counter==1) begin
            shift_reg <= {rxfifo_epu_din[7:0],shift_reg[23:8]};
        end
        /* After send the last byte, the shift_reg reset to 0 */
        else if(rxfifo_epu_empty && cdc_counter==7) begin
            shift_reg <= 24'b0;
        end
    end

    // flen_counter: counts the number of bytes in the frame
    reg [10:0] flen_counter;

    always @(posedge epu_clk or negedge rst_n)begin
        if(!rst_n) begin
            flen_counter <= 11'b0;
        end 
        else if(rxfifo_ready && cdc_counter==1 && state!=`IDLE && state!=`PREAMBLE) begin
            flen_counter <= flen_counter + 1'b1;
        end
        else if(rxfifo_epu_empty && cdc_counter==7) begin
            flen_counter <= 11'b0;
        end
    end

    // len_counter: counts the number of bytes in current state
    reg [10:0] len_counter;
    wire state_change;

    always@(posedge epu_clk or negedge rst_n)begin
        if(!rst_n) begin
            len_counter <= 11'b0;
        end 
        else if(state_change) begin
            len_counter <= 11'b0;
        end
        else if(rxfifo_ready && cdc_counter==1) begin
            len_counter <= len_counter + 1'b1;
        end
        else if(rxfifo_epu_empty && cdc_counter==7) begin
            len_counter <= 11'b0;
        end
    end

    /*****************************************************************
     * Statet machine for processing EtherCAT frames
     *****************************************************************/
    wire [10:0] payload_len;
    wire more_datagram;
    wire [31:0] logical_start_address;
    wire [1:0] operation;
    wire [1:0] cmd_type;
    wire circulating_frame;

    epu_state_machine epu_state_machine_inst(
        .clk(epu_clk),
        .rst_n(rst_n),
        .cdc_counter(cdc_counter),
        .shift_reg(shift_reg),
        .len_counter(len_counter),
        .payload_len(payload_len),
        .more_datagram(more_datagram),
        .circulating_frame(circulating_frame),
        .state_change(state_change),
        .state(state)
    );

    /*****************************************************************
     * EtherCAT Header processing
     *****************************************************************/
    wire [15:0] configured_station_address_r;
    wire [15:0] configured_station_alias_r;

    wire [15:0] ecat_event_mask_r;
    wire [31:0] al_event_mask_r;
    wire [15:0] ecat_event_request_r;
    wire [31:0] al_event_request_r;

    ethercat_header_process ethercat_header_process_inst(
        .epu_clk(epu_clk),
        .rst_n(rst_n),
        .state(state),
        .cdc_counter(cdc_counter),
        .shift_reg(shift_reg),
        .len_counter(len_counter),
        .configured_station_address_r(configured_station_address_r),
        .configured_station_alias_r(configured_station_alias_r),
        .payload_len(payload_len),
        .more_datagram(more_datagram),
        .logical_start_address(logical_start_address),
        .operation(operation),
        .cmd_type(cmd_type),
        .circulating_frame(circulating_frame)
    );

    /*****************************************************************
     * EPU to TX
    *****************************************************************/
    epu_tx_process epu_tx_process_inst(
        .epu_clk(epu_clk),
        .rst_n(rst_n),
        .cdc_counter(cdc_counter),
        .shift_reg(shift_reg),
        .len_counter(len_counter),
        .state_change(state_change),
        .state(state),
        .ecat_event_mask_r(ecat_event_mask_r),
        .ecat_event_request_r(ecat_event_request_r),
        .epu_tx_clk(epu_tx_clk),
        .epu_tx_dout(epu_tx_dout));
    /*****************************************************************
     * syncmanager
    *****************************************************************/
    wire [7:0] epu_pdi_dout_syncmanager;
    wire epu_pdi_dout_valid_syncmanager;
    wire [7:0] epu_ecat_dout_syncmanager;
    wire epu_ecat_dout_valid_syncmanager;
    syncmanager syncmanager_inst(
        .epu_clk(epu_clk),
        .rst_n(rst_n),

        .pdi_epu_re(pdi_epu_re),
        .pdi_epu_we(pdi_epu_we),
        .pdi_epu_addr(pdi_epu_addr),
        .pdi_epu_din(pdi_epu_din),
        .epu_pdi_dout(epu_pdi_dout_syncmanager),
        .epu_pdi_dout_valid(epu_pdi_dout_valid_syncmanager),

        .ecat_epu_re(ecat_epu_re),
        .ecat_epu_we(ecat_epu_we),
        .ecat_epu_addr(ecat_epu_addr),
        .ecat_epu_din(ecat_epu_din),
        .epu_ecat_dout(epu_ecat_dout_syncmanager),
        .epu_ecat_dout_valid(epu_ecat_dout_valid_syncmanager)
    );
    /*****************************************************************
     * esc information registers
    *****************************************************************/
    wire [7:0] epu_pdi_dout_esc_information;
    wire epu_pdi_dout_valid_esc_information;
    wire [7:0] epu_ecat_dout_esc_information;
    wire epu_ecat_dout_valid_esc_information;

    esc_information_registers esc_information_registers_inst(
        .epu_clk(epu_clk),
        .rst_n(rst_n),

        .pdi_epu_re(pdi_epu_re),
        .pdi_epu_addr(pdi_epu_addr),
        .epu_pdi_dout(epu_pdi_dout_esc_information),
        .epu_pdi_dout_valid(epu_pdi_dout_valid_esc_information),

        .ecat_epu_re(ecat_epu_re),
        .ecat_epu_addr(ecat_epu_addr),
        .epu_ecat_dout(epu_ecat_dout_esc_information),
        .epu_ecat_dout_valid(epu_ecat_dout_valid_esc_information)
    );

    /*****************************************************************
     * station address registers
    *****************************************************************/
    wire [7:0] epu_pdi_dout_station_address;
    wire epu_pdi_dout_valid_station_address;
    wire [7:0] epu_ecat_dout_station_address;
    wire epu_ecat_dout_valid_station_address;

    station_address_registers station_address_registers_inst(
        .epu_clk(epu_clk),
        .rst_n(rst_n),

        .pdi_epu_we(pdi_epu_we),
        .pdi_epu_re(pdi_epu_re),
        .pdi_epu_addr(pdi_epu_addr),
        .pdi_epu_din(pdi_epu_din),
        .epu_pdi_dout(epu_pdi_dout_station_address),
        .epu_pdi_dout_valid(epu_pdi_dout_valid_station_address),

        .ecat_epu_we(ecat_epu_we),
        .ecat_epu_re(ecat_epu_re),
        .ecat_epu_addr(ecat_epu_addr),
        .ecat_epu_din(ecat_epu_din),
        .epu_ecat_dout(epu_ecat_dout_station_address),
        .epu_ecat_dout_valid(epu_ecat_dout_valid_station_address),

        .configured_station_address_r(configured_station_address_r),
        .configured_station_alias_r(configured_station_alias_r),

        .crc_check_success(crc_check_success)
    );
    /*****************************************************************
    * interrupts registers
    *****************************************************************/
    wire [7:0] epu_pdi_dout_interrupts;
    wire epu_pdi_dout_valid_interrupts;
    wire [7:0] epu_ecat_dout_interrupts;
    wire epu_ecat_dout_valid_interrupts;

    interrupts_registers interrupts_registers_inst(
        .epu_clk(epu_clk),
        .rst_n(rst_n),

        .pdi_epu_we(pdi_epu_we),
        .pdi_epu_re(pdi_epu_re),
        .pdi_epu_addr(pdi_epu_addr),
        .pdi_epu_din(pdi_epu_din),
        .epu_pdi_dout(epu_pdi_dout_interrupts),
        .epu_pdi_dout_valid(epu_pdi_dout_valid_interrupts),

        .ecat_epu_we(ecat_epu_we),
        .ecat_epu_re(ecat_epu_re),
        .ecat_epu_addr(ecat_epu_addr),
        .ecat_epu_din(ecat_epu_din),
        .epu_ecat_dout(epu_ecat_dout_interrupts),
        .epu_ecat_dout_valid(epu_ecat_dout_valid_interrupts),

        .ecat_event_mask_r(ecat_event_mask_r),
        .al_event_mask_r(al_event_mask_r),
        .ecat_event_request_r(ecat_event_request_r),
        .al_event_request_r(al_event_request_r),

        .crc_check_success(crc_check_success)
    );
    /*****************************************************************
     * local address space access
    *****************************************************************/
    local_address_space_access epu_local_address_space_access_inst(
        .epu_clk(epu_clk),
        .rst_n(rst_n),

        .pdi_epu_re(pdi_epu_re),
        .pdi_epu_addr(pdi_epu_addr),
        .epu_pdi_dout(epu_pdi_dout),
        .epu_pdi_dout_valid(epu_pdi_dout_valid),

        .epu_pdi_dout_esc_information(epu_pdi_dout_esc_information),
        .epu_pdi_dout_valid_esc_information(epu_pdi_dout_valid_esc_information),
        .epu_pdi_dout_station_address(epu_pdi_dout_station_address),
        .epu_pdi_dout_valid_station_address(epu_pdi_dout_valid_station_address),
        .epu_pdi_dout_write_protection(),
        .epu_pdi_dout_valid_write_protection(),
        .epu_pdi_dout_datalink_layer(),
        .epu_pdi_dout_valid_datalink_layer(),
        .epu_pdi_dout_application_layer(),
        .epu_pdi_dout_valid_application_layer(),
        .epu_pdi_dout_configuration(),
        .epu_pdi_dout_valid_configuration(),
        .epu_pdi_dout_interrupts(),
        .epu_pdi_dout_valid_interrupts(),
        .epu_pdi_dout_error_counters(),
        .epu_pdi_dout_valid_error_counters(),
        .epu_pdi_dout_watchdogs(),
        .epu_pdi_dout_valid_watchdogs(),
        .epu_pdi_dout_sii_eeprom_interface(),
        .epu_pdi_dout_valid_sii_eeprom_interface(),
        .epu_pdi_dout_mii_management_interface(),
        .epu_pdi_dout_valid_mii_management_interface(),
        .epu_pdi_dout_fmmu(),
        .epu_pdi_dout_valid_fmmu(),
        .epu_pdi_dout_syncmanager(),
        .epu_pdi_dout_valid_syncmanager(),
        .epu_pdi_dout_distributed_clock(),
        .epu_pdi_dout_valid_distributed_clock(),

        .ecat_epu_re(ecat_epu_re),
        .ecat_epu_addr(ecat_epu_addr),
        .epu_ecat_dout(epu_ecat_dout),
        .epu_ecat_dout_valid(epu_ecat_dout_valid_station_address),
        .epu_ecat_dout_write_protection(),
        .epu_ecat_dout_valid_write_protection(),
        .epu_ecat_dout_datalink_layer(),
        .epu_ecat_dout_valid_datalink_layer(),
        .epu_ecat_dout_application_layer(),
        .epu_ecat_dout_valid_application_layer(),
        .epu_ecat_dout_configuration(),
        .epu_ecat_dout_valid_configuration(),
        .epu_ecat_dout_interrupts(),
        .epu_ecat_dout_valid_interrupts(),
        .epu_ecat_dout_error_counters(),
        .epu_ecat_dout_valid_error_counters(),
        .epu_ecat_dout_watchdogs(),
        .epu_ecat_dout_valid_watchdogs(),
        .epu_ecat_dout_sii_eeprom_interface(),
        .epu_ecat_dout_valid_sii_eeprom_interface(),
        .epu_ecat_dout_mii_management_interface(),
        .epu_ecat_dout_valid_mii_management_interface(),
        .epu_ecat_dout_fmmu(),
        .epu_ecat_dout_valid_fmmu(),
        .epu_ecat_dout_syncmanager(),
        .epu_ecat_dout_valid_syncmanager(),
        .epu_ecat_dout_distributed_clock(),
        .epu_ecat_dout_valid_distributed_clock(),

        .epu_ecat_dout_esc_information(epu_ecat_dout_esc_information),
        .epu_ecat_dout_valid_esc_information(epu_ecat_dout_valid_esc_information),
        .epu_ecat_dout_station_address(epu_ecat_dout_station_address),
        .epu_ecat_dout_valid_station_address(epu_ecat_dout_valid_station)

    );

endmodule